/*

    file:   video.c
    desc:   Video sync signal generator.

    note:   the algorthim is well optimized. I spent a lot of time searching the best way.

    author: Jaromir Dvorak (md@unicode.cz)

    This file is part of the AVGA platform.
    http://avga.prometheus4.com/


    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/



#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "video.h"
#include "driver.h"
#include "sound.h"

unsigned int c0;
unsigned char line;
unsigned char scroll;
volatile unsigned char wsync;
void *sp, *ss;

#ifdef VIDEO_USE_WINDOW
#include "../utils/window.h"

unsigned char height;
WINDOW aw;

static inline void __video_load_next_window(unsigned char* ptr)
{
	register unsigned char tmp;

	tmp = *ptr++;
	line = tmp;
	tmp = *ptr++;
	height = tmp;
	tmp = *ptr++;
	scroll = tmp;

#ifdef WINDOW_INDIVIDUAL_TILESET
	register unsigned int tmp1;
	asm volatile("ld %A0, %a1+ \r\n ld %B0, %a1+" : "=r" (tmp1) : "e" (ptr) );
	_pgmp = tmp1;
#endif

#ifdef WINDOW_INDIVIDUAL_REFTABLE
	register unsigned int tmp2;
	asm volatile("ld %A0, %a1+ \r\n ld %B0, %a1+" : "=r" (tmp2) : "e" (ptr) );
	_scrp = tmp2;
#endif

	aw = (WINDOW)ptr;
}


#else


unsigned char video_startline=0;
unsigned char video_params=0xF8;

void video_set_colormask(unsigned char mask)
{
	register unsigned char tmp = video_params;
	tmp&=~0xF0;
	mask&=0xF0;
	tmp|=mask;
	video_params = tmp;
}

static inline void video_set_scroll_x(unsigned char x)
{
	register unsigned char tmp = video_params;
	tmp&=~7;
	x&=7;
	tmp|=x;
	video_params = tmp;
}
static inline signed char video_rel_scroll_x(signed char x)
{
	register unsigned char xs = video_params;
	x+=xs&7;
	xs&=~7;
	xs|=x&7;
	video_params = xs;
	return x;
}
#if DRIVER_BLOCK_HEIGHT == 8
static inline signed char video_set_scroll_y(unsigned char yp)
{
	register unsigned char tmp = video_startline, y=yp;
	tmp&=~7; y&=7;	tmp|=y;
	video_startline = tmp;
}
static inline signed char video_rel_scroll_y(signed char yp)
{
	register unsigned char tmp = video_startline, y=yp;
	y+=tmp&7; tmp&=~7; tmp|=y&7;
	return y;
}
#else
static inline void video_set_scroll_y( unsigned char y)
{
	register unsigned char tmp = video_startline;
	tmp -= DRIVER_Y2PRE(tmp);
	y = DRIVER_Y2PRE(y);
	tmp += y;
	video_startline = tmp;
}
static inline signed char video_rel_scroll_y(signed char y)
{
	register unsigned char tmp = video_startline;
	y += DRIVER_Y2PRE(tmp);
	tmp -= DRIVER_Y2PRE(tmp);
	tmp += DRIVER_Y2PRE(y);
	video_startline = tmp;
	return y;
}
#endif


#if defined(HSCROLL) || defined(VSCROLL)

void video_set_scroll(unsigned char x, unsigned char y)
{
	video_set_scroll_x(x);
	video_set_scroll_y(y);
}

void video_scroll(signed char x, signed char y)
{
	video_rel_scroll_x(x);
	video_rel_scroll_y(y);
}

#endif
#endif

void video_init(sync_t *s)
{
	sp = ss = (unsigned char*)s;
	c0 = 0;
	__video_timer_setup_mode14();
	__video_timer_select_OVF_IRQ();

	video_set_xpos(VIDEO_XPOS);
#ifdef VIDEO_DYNAMIC_YPOS
	video_set_ypos(VIDEO_YPOS);
#endif
	VIDEO_TIMER_OCRB_DDR |= 1 << VIDEO_TIMER_OCRB_PIN;
#ifdef VIDEO_ENABLE_VSYNC
	VIDEO_VSYNC_DDR	|= 1 << VIDEO_VSYNC_PIN;
#endif
#if defined(VIDEO_USE_WINDOW) && defined(WINDOW_RESET)
	window_reset();
#endif

	//VIDEO_TIMER_ICR = VGA_LP;
	//VIDEO_TIMER_OCRB = VGA_SP;
}


unsigned int video_sync(void)
{
	unsigned int tick = 0;
	wsync = 0;
	while(!wsync) tick++;
	return tick;
}


ISR (VIDEO_TIMER_CMP_IRQ)
{
	if(scroll&0x08)
	{
		VIDEO_TIMER_FINE_SYNC();
		driver_throw_scanline(line, scroll);
	}

#ifdef SOUND_LINEHANDLE
	sound_line_handle();
#endif

	if(!--c0) //frame complete!
	{
		wsync = 1;
		__video_timer_select_OVF_IRQ();
		__video_timer_clear_OVF_IRQ();

		//VIDEO_VSYNC_PORT &= ~(1 << VIDEO_VSYNC_PIN);
		//c0 = 2;

//#ifdef SOUND_FRAMEHANDLE
//		sound_frame_handle();
//#endif
		return;
	}

#ifdef VIDEO_QUAD_SCANLINE
	if(c0&3) return; //only lines modulo 4 = 0
#endif
#ifdef VIDEO_DOUBLE_SCANLINE
	if(c0&1) return; //only even lines
#endif

	line++;

#ifdef VIDEO_USE_WINDOW
	if(!--height) //window complete!
	{
		__video_load_next_window((unsigned char*)aw);
		VIDEO_DDR = (scroll & 0xF0) | VIDEO_DEFAULT_DDR;
	}
#else
	if(line == DRIVER_RESY) scroll = 0xF0;
#endif

}

ISR (VIDEO_TIMER_OVF_IRQ)
{
	static unsigned int top;

	VIDEO_TIMER_ICR = top;

	if(!c0)
	{
		//VIDEO_VSYNC_PORT |= (1 << VIDEO_VSYNC_PIN);
		//c0 = 523;

		register unsigned int tmp;
		register uint8_t* ptr = sp;

s0:		asm volatile("lpm %A0, %a1+ \r\n lpm %B0, %a1+" : "=r" (tmp) : "e" (ptr) );
		if(!tmp) { ptr = ss; goto s0; }
		top = tmp;
		asm volatile("lpm %A0, %a1+ \r\n lpm %B0, %a1+" : "=r" (tmp) : "e" (ptr) );
		VIDEO_TIMER_OCRB = tmp;
		asm volatile("lpm %A0, %a1+ \r\n lpm %B0, %a1+" : "=r" (tmp) : "e" (ptr) );
		sp = ptr;

#ifdef VIDEO_ENABLE_VSYNC
		if(tmp & GVSYNC) 	VIDEO_VSYNC_PORT &= ~(1 << VIDEO_VSYNC_PIN);
		else		 		VIDEO_VSYNC_PORT |=  (1 << VIDEO_VSYNC_PIN);
		tmp&=0x7FFF;
#endif
		c0 = tmp;
	}

	if(--c0 == video_ypos)
	{

#ifdef VIDEO_USE_WINDOW
		__video_load_next_window((unsigned char*)window_get_default());
#else
		line = video_startline;
		scroll = video_params;
#endif

		VIDEO_DDR = (scroll & 0xF0) | VIDEO_DEFAULT_DDR;

		__video_timer_select_CMP_IRQ();
		__video_timer_clear_CMP_IRQ();
	}


#ifdef SOUND_LINEHANDLE
	sound_line_handle();
#endif

}





